#include "command.h"

#define BUFFERSIZE 4096

//私有通信协议， 因为TCP通信是字节流，没有边界，
//通过私有协议，能让发送接收方遵守确定的边界，接收数据
//定义一个小火车的私有协议

//获取文件长度信息
off_t statSize(int fileFd)
{
    struct stat statbuf;
    memset(&statbuf, 0, sizeof(statbuf));
    int ret = fstat(fileFd, &statbuf);
    if (-1 == ret) {
        perror("fstat");
        return -1;
    }
    return statbuf.st_size;
}

// 上传文件到服务器，需要一个用于通信的文件描述符。
// 新文件返回0， 断点续传返回1
int choiceHow(int peerfd, const command_t* pcom)
{
    train_t train;  //栈上
    memset(&train, 0, sizeof(train));

    //1、检查本地是否有这个文件，没有打印错误信息
    if (-1 == isInserver(pcom->name)) {
        puts("do not have this file");
        return -1;
    }
    printf("command：%s\n", pcom->command);
    //1、1有这个文件，执行上传, 发送上传命令消息
    //此处再把命令传给服务器，不然先发过去了，-------------重要
    //服务器也不知道你这个文件有没有，能不能发过
    //TCP通信是字节流协议，没有消息边界，不知道读到哪里作为消息的边界------最重要！
    //--发送命令也需要用小火车发送，不然接收端不知道读到哪里作为消息边界，特别是后面又跟数据来时

    //发送命令--用小火车
    train.length = strlen(pcom->command);
    strncpy(train.buf, pcom->command, train.length);
    //执行send发送命令给服务器-------
    int ret = sendn(peerfd, &train, 4 + train.length);  //4,length长度，train.length内容长度
    printf("send ret = %d\n", ret);
    if (-1 == ret) {
        puts("sendn failed 0");
        return -1;
    }

    //接收服务器返回的消息，采用哪种方式上传
    int tag;
    ret = recv(peerfd, &tag, sizeof(tag), MSG_WAITALL);
    ERROR_CHECK(ret, -1, "recv in choice");

    if (0 == tag) {
        puts("断点续传");
        putsFileInterrupt(peerfd, pcom->name);
        return 1;
    }
    else {
        puts("新文件传输");
        putsfile(peerfd, pcom->name);
        return 0;
    }
}

//断点续传，对端已经有文件的前提下--长度信息由偏移量获得的
int putsFileInterrupt(int peerfd, const char* filename)
{
    off_t file_lseek;
    off_t last_lseek;
    off_t left_lseek;
    train_t train;
    memset(&train, 0, sizeof(train));

    //接收对端当前文件位置--接收的长度信息，知道大小即字节流边界
    int ret = recv(peerfd, &file_lseek, sizeof(off_t), MSG_WAITALL);
    ERROR_CHECK(ret, -1, "recv in putsFileInterrupt");

    int fileFd = open(filename, O_RDONLY);
    ERROR_CHECK(fileFd, -1, "open in putsFileInterrupt");

    last_lseek = lseek(fileFd, -1, SEEK_END);
    //获取剩余文件长度, 将剩余文件长度发送给对端！！
    left_lseek = last_lseek - file_lseek;
    ret = send(peerfd, &left_lseek, sizeof(off_t), 0);

    //将文件偏移量设置到响应位置---重要
    lseek(fileFd, file_lseek, SEEK_SET);

    off_t total = 0;
    while (total < left_lseek) {
        memset(&train, 0, sizeof(train));
        ret = read(fileFd, train.buf, BUFFERSIZE);
        ERROR_CHECK(ret, -1, "send in putsFileInterrupt");
        if (0 == ret) {
            puts("bigfile send over");
            break;
        }

        train.length = ret;  //用ret记录读了多少字节的内容
        ret = sendn(peerfd, &train, 4 + train.length);
        if (-1 == ret) {
            puts("sendn failed in putsFileInterrupt");
        }

        total += train.length;
    }
    close(fileFd);
    return 0;
}

int putsfile(int fd, const char* filename)
{
    //本函数有两个文件描述符，
    //fdread是打开的本地文件的描述符
    //fd是用作TCP，socket网络通信的文件描述符
    //
    //打开文件
    int fdread = open(filename, O_RDONLY);
    if (-1 == fdread) {
        puts("open failed in putsfile");
        return -1;
    }
    //1）传文件名
    train_t train;  //栈上
    memset(&train, 0, sizeof(train));

    train.length = strlen(filename);
    strncpy(train.buf, filename, train.length);  //由输入的命令得来的文件名

    //按实际长度传过去的，并不是BUFFERSIZE + 4；
    int ret = sendn(fd, &train, 4 + train.length);
    if (-1 == ret) {
        //数据没有传完
        puts("sendn failed 1");
        return -1;
    }

    //2） 传文件长度
    //获取文件大小
    off_t file_size = statSize(fdread);
    printf(">> file length: %ld\n", file_size);

    //传文件长度
    memset(&train, 0, sizeof(train));
    train.length = sizeof(off_t);  //长度信息是off_t类型，//传过去的是字符串格式
    strncpy(train.buf, (char* )&file_size, sizeof(off_t));  //TCP字节流，这样传？
    ret = sendn(fd, &train, 4 + train.length);  //socket网络通信的文件描述符
    if (-1 == ret) {
        puts("sendn failed 2");
        return -1;
    }

    //3 传文件

    //if (file_size > 1<<20) {  //100<<20
    //    //采用mmap方式，需文件大小是确定的
    //    puts("mmap used");
    //    //省去了read
    //    char* pbuf = (char* )mmap(NULL, file_size, PROT_READ, MAP_SHARED, fdread, 0);
    //    ERROR_CHECK(pbuf, MAP_FAILED, "mmap pbuf");

    //    ret = sendn(fd, pbuf, file_size);
    //    if (-1 == ret) {
    //        puts("sendn in mmap failed");
    //        return -1;
    //    }
    //}
    //else {
        off_t total = 0;
        while (total < file_size) {
            memset(&train, 0, sizeof(train));
            ret = read(fdread, train.buf, BUFFERSIZE);
            if (0 == ret) {
                puts("bigfile send over");
                break;
            }
            ERROR_CHECK(ret, -1, "read");  //不想重写错误判断了
            train.length = ret;  //可能会产生问题-----

            ret = sendn(fd, &train, 4 + train.length);
            //返回的是发送成功的字节数
            if (-1 == ret) {
                puts("sendn failed 3");
                return -1;
            }
            total += train.length;
        }


    //}
    //4、打开一个文件描述符，记得关闭文件描述符
    close(fdread);
    return 0;
}

//下载文件

int recvFile(const char *filename,int peerfd){
    train_t train;
    bzero(&train,sizeof(train));
    int len;
    int fd=open(filename,O_RDWR);           //设置读写均可
    off_t filenow=lseek(fd,0,SEEK_END);                  //将文件偏移量设置到末尾
    int ret=send(peerfd,&filenow,sizeof(off_t),0);
    //接收剩余文件大小
    off_t fileLeft;
    ret=recv(peerfd,&fileLeft,sizeof(off_t),MSG_WAITALL);
    printf("file left;%ld.\n",fileLeft);

    off_t downloadSize=0;
    char recvBuff[BUFFERSIZE];
    while(downloadSize<fileLeft){               //接收文件
        ret=recv(peerfd,&len,4,MSG_WAITALL);
        memset(recvBuff,0,BUFFERSIZE);
        ret=recv(peerfd,recvBuff,len,MSG_WAITALL);
        downloadSize+=ret;
        ret=write(fd,recvBuff,ret);
        ERROR_CHECK(ret,-1,"write");
    }
    return 0;
}

int choiceFile(int peerfd,const char* filename){
    int tag=isInserver(filename);
    if(tag!=-1){
        send(peerfd,&tag,sizeof(tag),0);
        printf("断点续传。\n");
        recvFile(filename,peerfd);
        return 0;
    }else{
        send(peerfd,&tag,sizeof(tag),0);
        printf("新文件传输。\n");
        getFile(peerfd,filename);
        return -1;
    }
}

int isInserver(const char *filename){
    DIR *pdir=opendir(".");
    ERROR_CHECK(pdir,NULL,"opendir");

    struct dirent* pdirent;
    while((pdirent=readdir(pdir))!=NULL){           
        char *name=pdirent->d_name;
        if(strcmp(name,filename)==0){
            closedir(pdir);
            return 0;
        }
    }
    closedir(pdir);
    return -1;
}

int getFile(int fd,const char *filename){
    int length;
    int ret;
    char recvBuff[BUFFERSIZE]={0};

    //创建新文件
    int fileFd=open(filename,O_CREAT|O_RDWR,0666);
    ERROR_CHECK(fileFd,-1,"open");

    //接受文件的大小
    off_t fileSize;
    ret=recv(fd,&length,4,MSG_WAITALL);
    ERROR_CHECK(ret,-1,"recv");
    ret=recv(fd,&fileSize,sizeof(fileSize),MSG_WAITALL);
    ERROR_CHECK(ret, -1, "recv");
    printf("fileSize=%ld\n",fileSize); 

    //接收文件的内容i
    if (fileSize > 1<<20) {
        //mmap映射， 确定的文件大小
        puts("mmap ok");
        
        //ftruncate 文件空洞，让文件大小一样（骗人也骗自己）
        ftruncate(fileFd, fileSize);

        char* pbuf = (char* )mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fileFd, 0);
        ERROR_CHECK(pbuf, MAP_FAILED, "mmap pbuf");

        ret = recvn(fd, pbuf, fileSize);
        if (-1 == ret) {//  mmap不需要write;
            puts ("recvn in mmap failed");
            return -1;
        }
    }
    else {
        off_t downloadSize=0;
        while(downloadSize<fileSize){
            ret=recv(fd,&length,4,MSG_WAITALL);
            if(ret==0){
                printf("file %s has download all.\n",filename);
                break;
            }
            memset(recvBuff,0,BUFFERSIZE);
            ret=recv(fd,recvBuff,length,MSG_WAITALL);
            downloadSize+=ret;
            //写入本地
            ret=write(fileFd,recvBuff,ret);
            ERROR_CHECK(ret,-1,"write");
        }
    }

    printf("download all.\n");
    close(fileFd);
    return 0;
}

int sendn(int fd, const void* str, int len)
{
    char*  train= (char* )str;
    int left = len;
    int ret = -1;

    while (left > 0) {
        ret = send(fd, train, left, 0);
        if (-1 == ret) {
            perror("send");
            return -1;
        }
        else {
            train += ret;
            left -= ret;
        }
    }
    return len - left;  //返回的是发送成功的字节数
}

//成功返回接收的字节数， 错误返回-1；
int recvn(int fd, const void* str, int len)
{
    char* buf = (char* )str;
    int left = len;
    int ret = -1;

    while (left > 0) {
        ret = recv(fd, buf, left, 0);
        if (-1 == ret) {
            perror("recv");
            return -1;
        }
        buf += ret;
        left -= ret;
    }
    return len -left;
}
